package com.jorado.trace;

import brave.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import zipkin.Endpoint;
import zipkin.Span;
import zipkin.reporter.Reporter;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.Random;


public abstract class Platform implements Clock, Reporter<Span> {

    public static Logger LOGGER = LoggerFactory.getLogger(Platform.class);

    private static final Platform PLATFORM = findPlatform();

    private final long createTimestamp;
    private final long createTick;
    private volatile Endpoint localEndpoint;

    Platform() {
        createTimestamp = System.currentTimeMillis() * 1000;
        createTick = System.nanoTime();
    }

    @Override
    public void report(zipkin.Span span) {
        if (LOGGER.isInfoEnabled() && span != null) {
            //LOGGER.traceInfo(span.toString());
        }
    }

    public Endpoint localEndpoint() {
        if (localEndpoint == null) {
            synchronized (this) {
                if (localEndpoint == null) {
                    localEndpoint = produceLocalEndpoint();
                }
            }
        }
        return localEndpoint;
    }

    Endpoint produceLocalEndpoint() {
        Endpoint.Builder builder = Endpoint.builder().serviceName("unknown");
        try {
            Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
            if (nics == null) {
                return builder.build();
            }
            while (nics.hasMoreElements()) {
                NetworkInterface nic = nics.nextElement();
                Enumeration<InetAddress> addresses = nic.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    if (address.isSiteLocalAddress()) {
                        byte[] addressBytes = address.getAddress();
                        if (addressBytes.length == 4) {
                            builder.ipv4(ByteBuffer.wrap(addressBytes).getInt());
                        } else if (addressBytes.length == 16) {
                            builder.ipv6(addressBytes);
                        }
                        break;
                    }
                }
            }
        } catch (Exception exp) {
            LOGGER.warn("brave endpoint exception", exp);
        }
        return builder.build();
    }

    public static Platform get() {
        return PLATFORM;
    }

    private static Platform findPlatform() {
        Platform jre7 = Jre7.buildIfSupported();
        if (jre7 != null) {
            return jre7;
        }
        return Jre6.build();
    }

    public abstract long randomLong();

    @Override
    public long currentTimeMicroseconds() {
        return ((System.nanoTime() - createTick) / 1000) + createTimestamp;
    }

    static abstract class Jre7 extends Platform {

        static Jre7 buildIfSupported() {
            try {
                Class.forName("java.util.concurrent.ThreadLocalRandom");
                return new Jre7Platform();
            } catch (ClassNotFoundException e) {

            }
            return null;
        }

        @Override
        public long randomLong() {
            return java.util.concurrent.ThreadLocalRandom.current().nextLong();
        }
    }

    static abstract class Jre6 extends Platform {
        abstract Random prng();

        static Jre6 build() {
            return new Jre6Platform(new Random(System.nanoTime()));
        }

        @Override
        public long randomLong() {
            return prng().nextLong();
        }
    }
}
